﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using FinalFantasy6Randomizer.Properties;

namespace FinalFantasy6Randomizer
{
    class Randomizer
    {
        private static SeedRandom random;
        private readonly int seed;

        private List<string> maleNames;
        private List<string> femaleNames;
        private List<MenuItem> commands;
        private int morphCharacterId = -1;
        private int runicCharacterId = -1;
        private int blitzCharacterId = -1;
        private int swdTechCharacterId = -1;
        private int umaroCharacterId = -1;
        private int gogoCharacterId = -1;
        private int loreCharacterId = -1;
        private int danceCharacterId = -1;
        private int rageCharacterId = -1;
        private int stealCharacterId = -1;
        private int slotCharacterId = -1;
        private int sketchCharacterId = -1;
        private MenuItem umaroThird = MenuItem.Empty;

        private readonly StringBuilder espers = new StringBuilder();

        public Randomizer(int seed)
        {
            random = new SeedRandom(seed);
            this.seed = seed;
        }

        private static byte[] StringToByteArray(string input)
        {
            var retVal = new byte[input.Length];
            var i = 0;

            foreach (var ch in input)
            {
                retVal[i] = (byte)ch;
                i++;
            }

            return retVal;
        }

        private ItemType GetItemType(Item item)
        {
            var iNum = (int)item;

            if (iNum <= 0x09 || (iNum >= 0x25 && iNum <= 0x2a))
                return ItemType.Dirk;
            if (iNum >= 0x0a && iNum <= 0x1c)
                return ItemType.Sword;
            if (iNum >= 0x1d && iNum <= 0x24)
                return ItemType.Spear;
            if (iNum >= 0x2b && iNum <= 0x32)
                return ItemType.Knife;
            if (iNum >= 0x33 && iNum <= 0x3c)
                return ItemType.Rod;
            if (iNum >= 0x3d && iNum <= 0x40)
                return ItemType.Brush;
            if (iNum >= 0x41 && iNum <= 0x43)
                return ItemType.Star;
            if (iNum >= 0x44 && iNum <= 0x4c)
                return ItemType.Special;
            if (iNum >= 0x4d && iNum <= 0x52)
                return ItemType.Gambler;
            if (iNum >= 0x53 && iNum <= 0x59)
                return ItemType.Claw;
            if (iNum >= 0x5a && iNum <= 0x68)
                return ItemType.Shield;
            if (iNum >= 0x69 && iNum <= 0x83)
                return ItemType.Helmet;
            if (iNum >= 0x84 && iNum <= 0xa2)
                return ItemType.Armor;
            if (iNum >= 0xa3 && iNum <= 0xaa)
                return ItemType.Tool;
            if (iNum >= 0xab && iNum <= 0xaf)
                return ItemType.Skean;
            if (iNum >= 0xb0 && iNum <= 0xe6)
                return ItemType.Relic;
            return ItemType.Item;
        }

        public void CreateRom(string filename)
        {
            if (filename.Contains('\\') && !Directory.Exists(filename.Substring(0, filename.LastIndexOf('\\'))))
                Directory.CreateDirectory(filename.Substring(0, filename.LastIndexOf('\\')));

            do
            {
                ResetObjects();
                InitializeNames();
                InitializeCommands();
                RandomizeCharacters();
            } while (gogoCharacterId == 13);

            RandomizeEquipment();
            RandomizeStartingEquipment();
            RandomizeTreasures();
            WriteRom(filename);
            WriteLog(filename + ".txt");
        }

        private void ResetObjects()
        {
            AllCharacters.GetAllCharacters().Reset();
            EquippableItems.GetEquippableItems().Reset();
            TreasureBoxes.GetAllTreasureBoxes().Reset();
            StartingRages.GetStartingRages().Reset();
        }

        private void RandomizeTreasures()
        {
            var treasures = TreasureBoxes.GetAllTreasureBoxes().AllTreasureBoxes;
            var treasureTiers = TreasureTiers.GetTreasureTiers();
            var totalTiers = treasureTiers.ItemTiers.Count;

            foreach (var treasureBox in treasures)
            {
                var chestTier = 0;

                switch (treasureBox.ChestType)
                {
                    case ChestType.Monster:
                        Item chestItem;

                        switch ((int)treasureBox.Value)
                        {
                            case 0x00:
                                chestItem = Item.Tonic;
                                break;
                            case 0x20:
                                chestItem = Item.GreenBeret;
                                break;
                            case 0x25:
                                chestItem = Item.Hardened;
                                break;
                            case 0x96:
                                chestItem = Item.XPotion;
                                break;
                            case 0x22:
                                chestItem = Item.HyperWrist;
                                break;
                            case 0x5f:
                                chestItem = Item.Offering;
                                break;
                            case 0x71:
                                chestItem = Item.Graedus;
                                break;
                            case 0x74:
                                chestItem = Item.DragonClaw;
                                break;
                            case 0x98:
                                chestItem = Item.Minerva;
                                break;
                            case 0x99:
                                chestItem = Item.TigerFangs;
                                break;
                            default:
                                chestItem = Item.Empty;
                                break;
                        }

                        for (int j = 0; j < totalTiers; j++)
                        {
                            if (treasureTiers.ItemTiers[j].Contains(chestItem))
                            {
                                chestTier = j;
                                break;
                            }
                        }
                        break;
                    case ChestType.GP:
                        switch ((int)treasureBox.Value)
                        {
                            case 0x05:
                                chestTier = 2;
                                break;
                            case 0x0a:
                            case 0x0f:
                                chestTier = 4;
                                break;
                            case 0x32:
                                chestTier = 7;
                                break;
                            case 0x50:
                            case 0x82:
                            case 0xc8:
                                chestTier = 8;
                                break;
                        }
                        break;
                    case ChestType.Empty:
                        chestTier = 0;
                        break;
                    case ChestType.Item:
                        // Need to make sure Shadow is recruitable in the World of Ruin.
                        if (treasureBox.Value == Item.Striker)
                            continue;
                        for (int j = 0; j < totalTiers; j++)
                        {
                            if (treasureTiers.ItemTiers[j].Contains(treasureBox.Value))
                            {
                                chestTier = j;
                                break;
                            }
                        }
                        break;
                }

                if (chestTier == 0)
                {
                    if (random.Next(10) < 2)
                    {
                        chestTier += 1;
                    }
                }
                else
                {
                    var bump = random.Next(10);

                    if (bump < 1)
                    {
                        chestTier -= 1;
                    }
                    else if (bump < 2)
                    {
                        chestTier += 1;
                    }
                }

                bool placeGold = false;

                if (chestTier < 9 && chestTier != 6)
                {
                    placeGold = random.Next(20) < 1;
                }

                Item newValue;
                ChestType newType;

                if (placeGold)
                {
                    newType = ChestType.GP;

                    switch (chestTier)
                    {
                        case 0:
                            newValue = (Item)random.Next(1, 3);
                            break;
                        case 1:
                            newValue = (Item)random.Next(3, 5);
                            break;
                        case 2:
                            newValue = (Item)random.Next(5, 7);
                            break;
                        case 3:
                            newValue = (Item)random.Next(7, 9);
                            break;
                        case 4:
                            newValue = (Item)random.Next(9, 19);
                            break;
                        case 5:
                            newValue = (Item)random.Next(19, 39);
                            break;
                        case 7:
                            newValue = (Item)random.Next(39, 79);
                            break;
                        case 8:
                            newValue = (Item)random.Next(79, 201);
                            break;
                        default:
                            newValue = (Item)1;
                            break;
                    }

                }
                else
                {
                    var newItemList = treasureTiers.GiveItemTiers[chestTier];
                    var newItem = newItemList[random.Next(newItemList.Count)];
                    switch (newItem)
                    {
                        case Item.Empty:
                            newType = ChestType.Empty;
                            newValue = 0x00;
                            break;
                        case Item.Potion:
                        case Item.GreenCherry:
                        case Item.Offering:
                        case Item.Graedus:
                        case Item.Hardened:
                        case Item.TigerFangs:
                        case Item.DragonClaw:
                        case Item.Minerva:
                        case Item.HyperWrist:
                        case Item.GreenBeret:
                        case Item.Tonic:
                            {
                                var miab = random.Next(2) < 1;

                                if (miab)
                                {
                                    newType = ChestType.Monster;
                                    switch (newItem)
                                    {
                                        case Item.Tonic:
                                            newValue = (Item)TreasurePack.Lobo;
                                            break;
                                        case Item.GreenBeret:
                                            newValue = (Item)TreasurePack.TelstarSoldierSoldierSoldierSoldier;
                                            break;
                                        case Item.HyperWrist:
                                            newValue = (Item)TreasurePack.Specter;
                                            break;
                                        case Item.Minerva:
                                            newValue = (Item)TreasurePack.PugsPugsPugs;
                                            break;
                                        case Item.DragonClaw:
                                            newValue = (Item)TreasurePack.PresenterWhelkhead;
                                            break;
                                        case Item.TigerFangs:
                                            newValue = (Item)TreasurePack.Allover;
                                            break;
                                        case Item.Hardened:
                                            newValue = (Item)TreasurePack.Gigantos;
                                            break;
                                        case Item.Graedus:
                                            newValue = (Item)TreasurePack.Masterpug;
                                            break;
                                        case Item.Offering:
                                            newValue = (Item)TreasurePack.Katanasoul;
                                            break;
                                        case Item.GreenCherry:
                                            newValue = (Item)TreasurePack.Siegfried;
                                            break;
                                        case Item.Potion:
                                            newValue = (Item)TreasurePack.LoboLoboMarshal;
                                            break;
                                        default:
                                            newValue = (Item)TreasurePack.Lobo;
                                            break;
                                    }
                                }
                                else
                                {
                                    newType = ChestType.Item;
                                    newValue = newItem;
                                }
                            }
                            break;
                        default:
                            newType = ChestType.Item;
                            newValue = newItem;
                            break;
                    }
                }

                treasureBox.ChestType = newType;
                treasureBox.Value = newValue;
            }
        }

        private void RandomizeStartingEquipment()
        {
            var characters = AllCharacters.GetAllCharacters().Characters;

            RandomizeStartingEquipment(characters[0], 0);
            RandomizeStartingEquipment(characters[1], 1);
            RandomizeStartingEquipment(characters[2], 2);
            RandomizeStartingEquipment(characters[3], 3);
            RandomizeStartingEquipment(characters[4], 4);
            RandomizeStartingEquipment(characters[5], 5);
            RandomizeStartingEquipment(characters[6], 6);
            RandomizeStartingEquipment(characters[7], 7);
            RandomizeStartingEquipment(characters[8], 8);
            RandomizeStartingEquipment(characters[9], 9);
            RandomizeStartingEquipment(characters[10], 10);
            RandomizeStartingEquipment(characters[11], 11);
            RandomizeStartingEquipment(characters[12], 12);
            RandomizeStartingEquipment(characters[13], 13);
        }

        private void RandomizeEquipment()
        {
            foreach (var invItem in EquippableItems.GetEquippableItems().Items.Where(invItem => invItem.ChangeEquips))
            {
                for (int i = 0; i < 14; i++)
                {
                    if (GetItemType(invItem.Type) == ItemType.Relic)
                    {
                        for (int j = 0; j < 14; j++)
                        {
                            invItem.CharacterIds.Add(j);
                        }
                    }
                    else if (random.Next(2) < 1)
                    {
                        invItem.CharacterIds.Add(i);
                    }
                }

                if (invItem.CharacterIds.Count == 0)
                {
                    invItem.CharacterIds.Add(random.Next(14));
                }
            }

            InventoryItem item;

            // Steal items
            item = EquippableItems.GetEquippableItems().Items[(int)Item.ThiefGlove];
            item.CharacterIds.Clear();
            item.CharacterIds.Add(stealCharacterId);
            item.CharacterIds.Add(gogoCharacterId);
            if (umaroThird == MenuItem.Steal)
            {
                item.CharacterIds.Add(13);
            }

            item = EquippableItems.GetEquippableItems().Items[(int)Item.SneakRing];
            if (!item.CharacterIds.Contains(stealCharacterId))
            {
                item.CharacterIds.Add(stealCharacterId);
            }
            if (!item.CharacterIds.Contains(gogoCharacterId))
            {
                item.CharacterIds.Add(gogoCharacterId);
            }
            if (!item.CharacterIds.Contains(13) && umaroThird == MenuItem.Steal)
            {
                item.CharacterIds.Add(13);
            }

            // Slot items
            item = EquippableItems.GetEquippableItems().Items[(int)Item.CoinToss];
            item.CharacterIds.Clear();
            item.CharacterIds.Add(slotCharacterId);
            item.CharacterIds.Add(gogoCharacterId);
            if (umaroThird == MenuItem.Slot)
            {
                item.CharacterIds.Add(13);
            }

            // Sketch items
            item = EquippableItems.GetEquippableItems().Items[(int)Item.Coronet];
            if (!item.CharacterIds.Contains(sketchCharacterId))
            {
                item.CharacterIds.Add(sketchCharacterId);
            }
            if (!item.CharacterIds.Contains(gogoCharacterId))
            {
                item.CharacterIds.Add(gogoCharacterId);
            }
            if (!item.CharacterIds.Contains(13) && umaroThird == MenuItem.Sketch)
            {
                item.CharacterIds.Add(13);
            }

            item = EquippableItems.GetEquippableItems().Items[(int)Item.Beret];
            if (!item.CharacterIds.Contains(sketchCharacterId))
            {
                item.CharacterIds.Add(sketchCharacterId);
            }
            if (!item.CharacterIds.Contains(gogoCharacterId))
            {
                item.CharacterIds.Add(gogoCharacterId);
            }
            if (!item.CharacterIds.Contains(13) && umaroThird == MenuItem.Sketch)
            {
                item.CharacterIds.Add(13);
            }

            item = EquippableItems.GetEquippableItems().Items[(int)Item.FakeMustache];
            item.CharacterIds.Clear();
            item.CharacterIds.Add(sketchCharacterId);
            item.CharacterIds.Add(gogoCharacterId);
            if (umaroThird == MenuItem.Sketch)
            {
                item.CharacterIds.Add(13);
            }

            // items that alter Fight
            item = EquippableItems.GetEquippableItems().Items[(int)Item.DragoonBoots];
            item.CharacterIds.Remove(rageCharacterId);
            item.CharacterIds.Remove(umaroCharacterId);

            item = EquippableItems.GetEquippableItems().Items[(int)Item.DragonHorn];
            item.CharacterIds.Remove(rageCharacterId);
            item.CharacterIds.Remove(umaroCharacterId);

            item = EquippableItems.GetEquippableItems().Items[(int)Item.Offering];
            item.CharacterIds.Remove(rageCharacterId);
            item.CharacterIds.Remove(umaroCharacterId);

            // items that alter Magic
            item = EquippableItems.GetEquippableItems().Items[(int)Item.GemBox];
            item.CharacterIds.Remove(umaroCharacterId);
            item.CharacterIds.Remove(13);

            item = EquippableItems.GetEquippableItems().Items[(int)Item.GoldHairpin];
            item.CharacterIds.Remove(umaroCharacterId);
            item.CharacterIds.Remove(13);

            item = EquippableItems.GetEquippableItems().Items[(int)Item.Economizer];
            item.CharacterIds.Remove(umaroCharacterId);
            item.CharacterIds.Remove(13);

            // items that affect Umaro
            item = EquippableItems.GetEquippableItems().Items[(int)Item.RageRing];
            item.CharacterIds.Clear();
            item.CharacterIds.Add(umaroCharacterId);

            item = EquippableItems.GetEquippableItems().Items[(int)Item.BlizzardOrb];
            item.CharacterIds.Clear();
            item.CharacterIds.Add(umaroCharacterId);
        }

        private void WriteTreasures(FileStream rom)
        {
            var treasures = TreasureBoxes.GetAllTreasureBoxes().AllTreasureBoxes;

            foreach (var treasureBox in treasures)
            {
                rom.Seek(treasureBox.Address, SeekOrigin.Begin);
                rom.Write(new[] { (byte)treasureBox.XPos, (byte)treasureBox.YPos, (byte)treasureBox.ChestId, (byte)treasureBox.TypeByte, (byte)treasureBox.Value }, 0, 5);
            }
        }


        private void WriteEsperMagic(FileStream rom)
        {
            var esperNames = new[]
                                 {
                                     "Ramuh",
                                     "Ifrit",
                                     "Shiva",
                                     "Siren",
                                     "Terrato",
                                     "Shoat",
                                     "Maduin",
                                     "Bismark",
                                     "Stray",
                                     "Palidor",
                                     "Tritoch",
                                     "Odin",
                                     "Raiden",
                                     "Bahamut",
                                     "Alexandr",
                                     "Crusader",
                                     "Ragnarok",
                                     "Kirin",
                                     "ZoneSeek",
                                     "Carbunkl",
                                     "Phantom",
                                     "Sraphim",
                                     "Golem",
                                     "Unicorn",
                                     "Fenrir",
                                     "Starlet",
                                     "Phoenix",
                                 };

            var esperNameCount = 0;

            for (int i = 0x186e00; i <= 0x186f1e; i += 0x0b)
            {
                rom.Seek(i, SeekOrigin.Begin);

                var rate1 = rom.ReadByte();
                var spell1 = rom.ReadByte();
                var rate2 = rom.ReadByte();
                var spell2 = rom.ReadByte();
                var rate3 = rom.ReadByte();
                var spell3 = rom.ReadByte();
                var rate4 = rom.ReadByte();
                var spell4 = rom.ReadByte();
                var rate5 = rom.ReadByte();
                var spell5 = rom.ReadByte();
                var bonus = rom.ReadByte();
                var esperSpellList = new List<Spell>();
                var esperRateList = new List<int>();

                RandomizeEsperSpell(esperSpellList, esperRateList, spell1);
                RandomizeEsperSpell(esperSpellList, esperRateList, spell2);
                RandomizeEsperSpell(esperSpellList, esperRateList, spell3);
                RandomizeEsperSpell(esperSpellList, esperRateList, spell4);
                RandomizeEsperSpell(esperSpellList, esperRateList, spell5);

                espers.AppendLine(string.Format("{0}:", esperNames[esperNameCount]));
                esperNameCount++;
                espers.AppendLine(string.Format("{0} x {1}", esperSpellList[0], esperRateList[0]));

                if ((int)esperSpellList[1] != 255)
                {
                    espers.AppendLine(string.Format("{0} x {1}", esperSpellList[1], esperRateList[1]));
                }

                if ((int)esperSpellList[2] != 255)
                {
                    espers.AppendLine(string.Format("{0} x {1}", esperSpellList[2], esperRateList[2]));
                }

                if ((int)esperSpellList[3] != 255)
                {
                    espers.AppendLine(string.Format("{0} x {1}", esperSpellList[3], esperRateList[3]));
                }

                if ((int)esperSpellList[4] != 255)
                {
                    espers.AppendLine(string.Format("{0} x {1}", esperSpellList[4], esperRateList[4]));
                }
                espers.AppendLine("");

                rom.Seek(i, SeekOrigin.Begin);
                rom.Write(new[] { (byte)esperRateList[0], (byte)esperSpellList[0], (byte)esperRateList[1], (byte)esperSpellList[1], (byte)esperRateList[2], (byte)esperSpellList[2], (byte)esperRateList[3], (byte)esperSpellList[3], (byte)esperRateList[4], (byte)esperSpellList[4] }, 0, 10);
            }
        }

        private static void RandomizeEsperSpell(List<Spell> esperSpellList, List<int> esperRateList, int spell)
        {
            if (MagicTiers.GetMagicTiers().Tiers[0].Contains((Spell)spell))
            {
                AddSpell(esperSpellList, MagicTiers.GetMagicTiers().Tiers[0]);
                esperRateList.Add(random.Next(1, 11) + random.Next(1, 11));
            }
            else if (MagicTiers.GetMagicTiers().Tiers[1].Contains((Spell)spell))
            {
                AddSpell(esperSpellList, MagicTiers.GetMagicTiers().Tiers[1]);
                esperRateList.Add(random.Next(1, 8) + random.Next(1, 8));
            }
            else if (MagicTiers.GetMagicTiers().Tiers[2].Contains((Spell)spell))
            {
                AddSpell(esperSpellList, MagicTiers.GetMagicTiers().Tiers[2]);
                esperRateList.Add(random.Next(1, 4) + random.Next(1, 4));
            }
            else if (MagicTiers.GetMagicTiers().Tiers[3].Contains((Spell)spell))
            {
                AddSpell(esperSpellList, MagicTiers.GetMagicTiers().Tiers[3]);
                esperRateList.Add(2);
            }
            else
            {
                esperSpellList.Add((Spell)spell);
                esperRateList.Add(0);
            }
        }

        private void WriteShops(FileStream rom)
        {
            for (int i = 0x47ac0; i <= 0x47dc6; i += 0x09)
            {
                rom.Seek(i, SeekOrigin.Begin);

                var shopType = rom.ReadByte();
                shopType &= 0x07;

                var item1 = rom.ReadByte();
                var item2 = rom.ReadByte();
                var item3 = rom.ReadByte();
                var item4 = rom.ReadByte();
                var item5 = rom.ReadByte();
                var item6 = rom.ReadByte();
                var item7 = rom.ReadByte();
                var item8 = rom.ReadByte();
                var itemList = new List<Item>();

                RandomizeItem(itemList, item1, shopType);
                RandomizeItem(itemList, item2, shopType);
                RandomizeItem(itemList, item3, shopType);
                RandomizeItem(itemList, item4, shopType);
                RandomizeItem(itemList, item5, shopType);
                RandomizeItem(itemList, item6, shopType);
                RandomizeItem(itemList, item7, shopType);
                RandomizeItem(itemList, item8, shopType);

                rom.Seek(i + 1, SeekOrigin.Begin);
                rom.Write(new[] { (byte)itemList[0], (byte)itemList[1], (byte)itemList[2], (byte)itemList[3], (byte)itemList[4], (byte)itemList[5], (byte)itemList[6], (byte)itemList[7] }, 0, 8);
            }
        }

        private void RandomizeItem(List<Item> itemList, int item, int shopType = -1)
        {
            var treasureTiers = TreasureTiers.GetTreasureTiers();
            var totalTiers = treasureTiers.ItemTiers.Count;
            var chestTier = 0;
            var matchShopType = (shopType > -1) && (random.Next(4) < 3);

            if (item != (int)Item.Empty && item != (int)Item.SprintShoes)
            {
                for (int j = 0; j < totalTiers; j++)
                {
                    if (treasureTiers.ItemTiers[j].Contains((Item)item))
                    {
                        chestTier = j;
                        break;
                    }
                }

                var newItemList = treasureTiers.GiveItemTiers[chestTier];
                var newItem = newItemList[random.Next(newItemList.Count)];
                var matchTimeout = 0;

                while (itemList.Contains(newItem) || newItem == Item.Empty || (matchShopType && !ItemMatchesShop(newItem, shopType)))
                {
                    newItem = newItemList[random.Next(newItemList.Count)];
                    matchTimeout++;
                    if (matchTimeout > 30)
                    {
                        matchShopType = false;
                    }
                }

                itemList.Add(newItem);
            }
            else
            {
                itemList.Add((Item)item);
            }

        }

        private bool ItemMatchesShop(Item newItem, int shopType)
        {
            var itemType = GetItemType(newItem);
            if (shopType == 1 && (itemType == ItemType.Dirk || itemType == ItemType.Sword || itemType == ItemType.Spear || itemType == ItemType.Knife || itemType == ItemType.Rod || itemType == ItemType.Brush || itemType == ItemType.Star || itemType == ItemType.Special || itemType == ItemType.Gambler || itemType == ItemType.Claw || itemType == ItemType.Tool))
                return true;
            if (shopType == 2 && (itemType == ItemType.Shield || itemType == ItemType.Helmet || itemType == ItemType.Armor))
                return true;
            if (shopType == 3 && (itemType == ItemType.Skean || itemType == ItemType.Item || itemType == ItemType.Tool))
                return true;
            if (shopType == 4 && (itemType == ItemType.Relic))
                return true;
            if (shopType < 1 || shopType > 4)
                return true;
            return false;
        }

        private void WriteLog(string filename)
        {
            var output = new StringBuilder();

            output.AppendLine("Final Fantasy VI Randomizer Log");
            output.AppendLine("-------------------------------");
            output.AppendLine(string.Format("Version: {0}", MainForm.Version));
            output.AppendLine(string.Format("Creation Date: {0}", DateTime.Now));
            output.AppendLine(string.Format("Seed: {0}", seed));
            output.AppendLine("");
            output.AppendLine("Terra");
            output.AppendLine("-----");
            WriteCharacterLog(output, AllCharacters.GetAllCharacters().Characters[0]);
            output.AppendLine("");
            output.AppendLine("");
            output.AppendLine("");
            output.AppendLine("Locke");
            output.AppendLine("-----");
            WriteCharacterLog(output, AllCharacters.GetAllCharacters().Characters[1]);
            output.AppendLine("");
            output.AppendLine("");
            output.AppendLine("");
            output.AppendLine("Cyan");
            output.AppendLine("-----");
            WriteCharacterLog(output, AllCharacters.GetAllCharacters().Characters[2]);
            output.AppendLine("");
            output.AppendLine("");
            output.AppendLine("");
            output.AppendLine("Shadow");
            output.AppendLine("-----");
            WriteCharacterLog(output, AllCharacters.GetAllCharacters().Characters[3]);
            output.AppendLine("");
            output.AppendLine("");
            output.AppendLine("");
            output.AppendLine("Edgar");
            output.AppendLine("-----");
            WriteCharacterLog(output, AllCharacters.GetAllCharacters().Characters[4]);
            output.AppendLine("");
            output.AppendLine("");
            output.AppendLine("");
            output.AppendLine("Sabin");
            output.AppendLine("-----");
            WriteCharacterLog(output, AllCharacters.GetAllCharacters().Characters[5]);
            output.AppendLine("");
            output.AppendLine("");
            output.AppendLine("");
            output.AppendLine("Celes");
            output.AppendLine("-----");
            WriteCharacterLog(output, AllCharacters.GetAllCharacters().Characters[6]);
            output.AppendLine("");
            output.AppendLine("");
            output.AppendLine("");
            output.AppendLine("Strago");
            output.AppendLine("-----");
            WriteCharacterLog(output, AllCharacters.GetAllCharacters().Characters[7]);
            output.AppendLine("");
            output.AppendLine("");
            output.AppendLine("");
            output.AppendLine("Relm");
            output.AppendLine("-----");
            WriteCharacterLog(output, AllCharacters.GetAllCharacters().Characters[8]);
            output.AppendLine("");
            output.AppendLine("");
            output.AppendLine("");
            output.AppendLine("Setzer");
            output.AppendLine("-----");
            WriteCharacterLog(output, AllCharacters.GetAllCharacters().Characters[9]);
            output.AppendLine("");
            output.AppendLine("");
            output.AppendLine("");
            output.AppendLine("Mog");
            output.AppendLine("-----");
            WriteCharacterLog(output, AllCharacters.GetAllCharacters().Characters[10]);
            output.AppendLine("");
            output.AppendLine("");
            output.AppendLine("");
            output.AppendLine("Gau");
            output.AppendLine("-----");
            WriteCharacterLog(output, AllCharacters.GetAllCharacters().Characters[11]);
            output.AppendLine("");
            output.AppendLine("");
            output.AppendLine("");
            output.AppendLine("Gogo");
            output.AppendLine("-----");
            WriteCharacterLog(output, AllCharacters.GetAllCharacters().Characters[12]);
            output.AppendLine("");
            output.AppendLine("");
            output.AppendLine("");
            output.AppendLine("Umaro");
            output.AppendLine("-----");
            WriteCharacterLog(output, AllCharacters.GetAllCharacters().Characters[13]);
            output.AppendLine("");
            output.AppendLine("");
            output.AppendLine("");
            output.AppendLine("Esper List");
            output.AppendLine("-----");
            output.Append(espers);
            output.AppendLine("");
            output.AppendLine("");
            output.AppendLine("");
            output.AppendLine("Treasure List");
            output.AppendLine("-----");
            WriteTreasureList(output);
            output.AppendLine("");


            using (var outStream = new StreamWriter(filename.Replace("<seed>", string.Format("{0:0000000}", seed))))
            {
                outStream.Write(output.ToString());
            }
        }

        private void WriteTreasureList(StringBuilder output)
        {
            var treasures = TreasureBoxes.GetAllTreasureBoxes().AllTreasureBoxes;
            var areas = new List<string>();

            foreach (var treasureBox in treasures)
            {
                if (!areas.Contains(treasureBox.Area))
                {
                    areas.Add(treasureBox.Area);
                }
            }

            foreach (var area in areas.OrderBy(x => x))
            {
                output.AppendLine(area);
                output.AppendLine("-----");
                foreach (var treasureBox in treasures.Where(x => x.Area == area))
                {
                    switch (treasureBox.ChestType)
                    {
                        case ChestType.Empty:
                            output.AppendLine("Empty");
                            break;
                        case ChestType.GP:
                            output.AppendLine(string.Format("{0}00 GP", (int)treasureBox.Value));
                            break;
                        case ChestType.Item:
                            output.AppendLine(treasureBox.Value.ToString());
                            break;
                        case ChestType.Monster:
                            output.AppendLine(string.Format("Monster-in-a-Box! ({0})", (TreasurePack)treasureBox.Value));
                            break;
                    }
                }
                output.AppendLine();
            }
        }

        private void WriteCharacterLog(StringBuilder output, Character character)
        {
            output.AppendLine(string.Format("Name: {0}", character.Name.Value));
            output.AppendLine("");
            output.AppendLine(string.Format("Palette: {0}", character.PaletteOverworld[0].Value));
            output.AppendLine("");
            output.AppendLine(string.Format("HP: {0}", character.HP.Value));
            output.AppendLine(string.Format("MP: {0}", character.MP.Value));
            output.AppendLine("");
            output.AppendLine("Menu:");
            output.AppendLine(character.Menu1.Value.ToString() == "Empty" ? "---" : character.Menu1.Value.ToString());
            output.AppendLine(character.Menu2.Value.ToString() == "Empty" ? "---" : character.Menu2.Value.ToString());
            output.AppendLine(character.Menu3.Value.ToString() == "Empty" ? "---" : character.Menu3.Value.ToString());
            output.AppendLine(character.Menu4.Value.ToString() == "Empty" ? "---" : character.Menu4.Value.ToString());
            output.AppendLine("");
            output.AppendLine(string.Format("Vigor: {0}", character.Vigor.Value));
            output.AppendLine(string.Format("Speed: {0}", character.Speed.Value));
            output.AppendLine(string.Format("Stamina: {0}", character.Stamina.Value));
            output.AppendLine(string.Format("Magic Power: {0}", character.MagicPower.Value));
            output.AppendLine(string.Format("Battle Power: {0}", character.BattlePower.Value));
            output.AppendLine(string.Format("Defense: {0}", character.Defense.Value));
            output.AppendLine(string.Format("Magic Defense: {0}", character.MagicDefense.Value));
            output.AppendLine(string.Format("Evade%: {0}", character.Evade.Value));
            output.AppendLine(string.Format("Magic Block%: {0}", character.MagicBlock.Value));
            output.AppendLine("");
            output.AppendLine("Equips:");
            output.AppendLine(character.Hand1.Value.ToString());
            output.AppendLine(character.Hand2.Value.ToString());
            output.AppendLine(character.Head.Value.ToString());
            output.AppendLine(character.Body.Value.ToString());
            output.AppendLine(character.Relic1.Value.ToString());
            output.AppendLine(character.Relic2.Value.ToString());
            output.AppendLine("");
            output.AppendLine(string.Format("Run Success: {0}", character.RunSuccess.Value & 3));
        }

        private void WriteRom(string filename)
        {
            using (var rom = new FileStream(filename.Replace("<seed>", string.Format("{0:0000000}", seed)), FileMode.OpenOrCreate))
            {
                rom.Write(Resources.RomImage, 0, 3145728);

                foreach (var character in AllCharacters.GetAllCharacters().Characters)
                {
                    if (character.Name != null)
                    {
                        WriteCharacter(rom, character);
                    }
                }

                //leave out Gogo and Umaro for now
                if (-1 < morphCharacterId && morphCharacterId < 12)
                {
                    WriteMorphBytes(rom);
                }

                //leave out Gogo and Umaro for now
                if (-1 < runicCharacterId && runicCharacterId < 12)
                {
                    WriteRunicBytes(rom);
                }

                if (blitzCharacterId > -1)
                {
                    WriteBlitzBytes(rom);
                }

                if (swdTechCharacterId > -1)
                {
                    WriteSwdTechBytes(rom);
                }

                if (loreCharacterId > -1)
                {
                    WriteLoreBytes(rom);
                }

                if (danceCharacterId > -1)
                {
                    WriteDanceBytes(rom);
                }

                if (umaroCharacterId > -1)
                {
                    WriteUmaroBytes(rom);
                }

                if (gogoCharacterId > -1)
                {
                    WriteGogoBytes(rom);
                }

                if (rageCharacterId > -1)
                {
                    WriteRageBytes(rom);
                }

                WriteTreasures(rom);
                WriteEquippableItems(rom);
                WriteStartingMagic(rom);
                WriteEsperMagic(rom);
                WriteShops(rom);
                WriteRngTable(rom);
                WriteSteals(rom);
                WriteColosseum(rom);

                WriteConfig(rom);

                WriteSeedInRom(rom);
            }
        }

        private void WriteSeedInRom(FileStream rom)
        {
            var seedStr = string.Format("FF6Rv{0} {1}", MainForm.Version, seed.ToString().PadLeft(7, '0')).PadRight(21).Substring(0, 21);

            rom.Seek(0xffc0, SeekOrigin.Begin);

            rom.Write(StringToByteArray(seedStr), 0, 21);
        }

        private void WriteConfig(FileStream rom)
        {
            byte byte1 = 0;
            byte byte2 = 0;
            byte byte3 = 0;
            byte byte4 = 0;

            switch (Settings.Default.BattleSpeed)
            {
                case "1 (Fastest)":
                    byte1 |= 0x00;
                    break;
                case "2":
                    byte1 |= 0x01;
                    break;
                case "3":
                    byte1 |= 0x02;
                    break;
                case "4":
                    byte1 |= 0x03;
                    break;
                case "5":
                    byte1 |= 0x04;
                    break;
                case "6 (Slowest)":
                    byte1 |= 0x05;
                    break;
            }

            if (Settings.Default.BattleMode == "Wait")
                byte1 |= 0x08;

            switch (Settings.Default.MessageSpeed)
            {
                case "1 (Fastest)":
                    byte1 |= 0x00;
                    break;
                case "2":
                    byte1 |= 0x10;
                    break;
                case "3":
                    byte1 |= 0x20;
                    break;
                case "4":
                    byte1 |= 0x30;
                    break;
                case "5":
                    byte1 |= 0x40;
                    break;
                case "6 (Slowest)":
                    byte1 |= 0x50;
                    break;
            }

            if (Settings.Default.CommandSet == "Short")
                byte1 |= 0x80;

            byte2 |= (byte)(int.Parse(Settings.Default.Window) - 0x01);

            if (Settings.Default.Reequip == "Empty")
                byte2 |= 0x10;
            if (Settings.Default.Sound == "Mono")
                byte2 |= 0x20;
            if (Settings.Default.Cursor == "Memory")
                byte2 |= 0x40;
            if (Settings.Default.Gauge == "Off")
                byte2 |= 0x80;

            switch (Settings.Default.MagicOrder)
            {
                case "Healing, Attack, Effect":
                    byte3 |= 0x00;
                    break;
                case "Healing, Effect, Attack":
                    byte3 |= 0x01;
                    break;
                case "Attack, Effect, Healing":
                    byte3 |= 0x02;
                    break;
                case "Attack, Healing, Effect":
                    byte3 |= 0x03;
                    break;
                case "Effect, Healing, Attack":
                    byte3 |= 0x04;
                    break;
                case "Effect, Attack, Healing":
                    byte3 |= 0x05;
                    break;
            }

            WriteFontColor(rom);
            WriteBgColors(rom);

            rom.Seek(0x3f0e0, SeekOrigin.Begin);
            rom.Write(new[] { byte1, byte2, byte3, byte4 }, 0, 4);
        }

        private void WriteBgColors(FileStream rom)
        {
            var bg = new List<List<Ff6Color>>
                     {
                         new List<Ff6Color>
                             {
                                 new Ff6Color(),
                                 new Ff6Color(),
                                 new Ff6Color(),
                                 new Ff6Color(),
                                 new Ff6Color(),
                                 new Ff6Color(),
                                 new Ff6Color(),
                             },
                         new List<Ff6Color>
                             {
                                 new Ff6Color(),
                                 new Ff6Color(),
                                 new Ff6Color(),
                                 new Ff6Color(),
                                 new Ff6Color(),
                                 new Ff6Color(),
                                 new Ff6Color(),
                             },
                         new List<Ff6Color>
                             {
                                 new Ff6Color(),
                                 new Ff6Color(),
                                 new Ff6Color(),
                                 new Ff6Color(),
                                 new Ff6Color(),
                                 new Ff6Color(),
                                 new Ff6Color(),
                             },
                         new List<Ff6Color>
                             {
                                 new Ff6Color(),
                                 new Ff6Color(),
                                 new Ff6Color(),
                                 new Ff6Color(),
                                 new Ff6Color(),
                                 new Ff6Color(),
                                 new Ff6Color(),
                             },
                         new List<Ff6Color>
                             {
                                 new Ff6Color(),
                                 new Ff6Color(),
                                 new Ff6Color(),
                                 new Ff6Color(),
                                 new Ff6Color(),
                                 new Ff6Color(),
                                 new Ff6Color(),
                             },
                         new List<Ff6Color>
                             {
                                 new Ff6Color(),
                                 new Ff6Color(),
                                 new Ff6Color(),
                                 new Ff6Color(),
                                 new Ff6Color(),
                                 new Ff6Color(),
                                 new Ff6Color(),
                             },
                         new List<Ff6Color>
                             {
                                 new Ff6Color(),
                                 new Ff6Color(),
                                 new Ff6Color(),
                                 new Ff6Color(),
                                 new Ff6Color(),
                                 new Ff6Color(),
                                 new Ff6Color(),
                             },
                         new List<Ff6Color>
                             {
                                 new Ff6Color(),
                                 new Ff6Color(),
                                 new Ff6Color(),
                                 new Ff6Color(),
                                 new Ff6Color(),
                                 new Ff6Color(),
                                 new Ff6Color(),
                             },
                     };

            bg[0][0].Red = Settings.Default.Bg1Color1R;
            bg[0][0].Green = Settings.Default.Bg1Color1G;
            bg[0][0].Blue = Settings.Default.Bg1Color1B;
            bg[0][1].Red = Settings.Default.Bg1Color2R;
            bg[0][1].Green = Settings.Default.Bg1Color2G;
            bg[0][1].Blue = Settings.Default.Bg1Color2B;
            bg[0][2].Red = Settings.Default.Bg1Color3R;
            bg[0][2].Green = Settings.Default.Bg1Color3G;
            bg[0][2].Blue = Settings.Default.Bg1Color3B;
            bg[0][3].Red = Settings.Default.Bg1Color4R;
            bg[0][3].Green = Settings.Default.Bg1Color4G;
            bg[0][3].Blue = Settings.Default.Bg1Color4B;
            bg[0][4].Red = Settings.Default.Bg1Color5R;
            bg[0][4].Green = Settings.Default.Bg1Color5G;
            bg[0][4].Blue = Settings.Default.Bg1Color5B;
            bg[0][5].Red = Settings.Default.Bg1Color6R;
            bg[0][5].Green = Settings.Default.Bg1Color6G;
            bg[0][5].Blue = Settings.Default.Bg1Color6B;
            bg[0][6].Red = Settings.Default.Bg1Color7R;
            bg[0][6].Green = Settings.Default.Bg1Color7G;
            bg[0][6].Blue = Settings.Default.Bg1Color7B;
            bg[1][0].Red = Settings.Default.Bg2Color1R;
            bg[1][0].Green = Settings.Default.Bg2Color1G;
            bg[1][0].Blue = Settings.Default.Bg2Color1B;
            bg[1][1].Red = Settings.Default.Bg2Color2R;
            bg[1][1].Green = Settings.Default.Bg2Color2G;
            bg[1][1].Blue = Settings.Default.Bg2Color2B;
            bg[1][2].Red = Settings.Default.Bg2Color3R;
            bg[1][2].Green = Settings.Default.Bg2Color3G;
            bg[1][2].Blue = Settings.Default.Bg2Color3B;
            bg[1][3].Red = Settings.Default.Bg2Color4R;
            bg[1][3].Green = Settings.Default.Bg2Color4G;
            bg[1][3].Blue = Settings.Default.Bg2Color4B;
            bg[1][4].Red = Settings.Default.Bg2Color5R;
            bg[1][4].Green = Settings.Default.Bg2Color5G;
            bg[1][4].Blue = Settings.Default.Bg2Color5B;
            bg[1][5].Red = Settings.Default.Bg2Color6R;
            bg[1][5].Green = Settings.Default.Bg2Color6G;
            bg[1][5].Blue = Settings.Default.Bg2Color6B;
            bg[1][6].Red = Settings.Default.Bg2Color7R;
            bg[1][6].Green = Settings.Default.Bg2Color7G;
            bg[1][6].Blue = Settings.Default.Bg2Color7B;
            bg[2][0].Red = Settings.Default.Bg3Color1R;
            bg[2][0].Green = Settings.Default.Bg3Color1G;
            bg[2][0].Blue = Settings.Default.Bg3Color1B;
            bg[2][1].Red = Settings.Default.Bg3Color2R;
            bg[2][1].Green = Settings.Default.Bg3Color2G;
            bg[2][1].Blue = Settings.Default.Bg3Color2B;
            bg[2][2].Red = Settings.Default.Bg3Color3R;
            bg[2][2].Green = Settings.Default.Bg3Color3G;
            bg[2][2].Blue = Settings.Default.Bg3Color3B;
            bg[2][3].Red = Settings.Default.Bg3Color4R;
            bg[2][3].Green = Settings.Default.Bg3Color4G;
            bg[2][3].Blue = Settings.Default.Bg3Color4B;
            bg[2][4].Red = Settings.Default.Bg3Color5R;
            bg[2][4].Green = Settings.Default.Bg3Color5G;
            bg[2][4].Blue = Settings.Default.Bg3Color5B;
            bg[2][5].Red = Settings.Default.Bg3Color6R;
            bg[2][5].Green = Settings.Default.Bg3Color6G;
            bg[2][5].Blue = Settings.Default.Bg3Color6B;
            bg[2][6].Red = Settings.Default.Bg3Color7R;
            bg[2][6].Green = Settings.Default.Bg3Color7G;
            bg[2][6].Blue = Settings.Default.Bg3Color7B;
            bg[3][0].Red = Settings.Default.Bg4Color1R;
            bg[3][0].Green = Settings.Default.Bg4Color1G;
            bg[3][0].Blue = Settings.Default.Bg4Color1B;
            bg[3][1].Red = Settings.Default.Bg4Color2R;
            bg[3][1].Green = Settings.Default.Bg4Color2G;
            bg[3][1].Blue = Settings.Default.Bg4Color2B;
            bg[3][2].Red = Settings.Default.Bg4Color3R;
            bg[3][2].Green = Settings.Default.Bg4Color3G;
            bg[3][2].Blue = Settings.Default.Bg4Color3B;
            bg[3][3].Red = Settings.Default.Bg4Color4R;
            bg[3][3].Green = Settings.Default.Bg4Color4G;
            bg[3][3].Blue = Settings.Default.Bg4Color4B;
            bg[3][4].Red = Settings.Default.Bg4Color5R;
            bg[3][4].Green = Settings.Default.Bg4Color5G;
            bg[3][4].Blue = Settings.Default.Bg4Color5B;
            bg[3][5].Red = Settings.Default.Bg4Color6R;
            bg[3][5].Green = Settings.Default.Bg4Color6G;
            bg[3][5].Blue = Settings.Default.Bg4Color6B;
            bg[3][6].Red = Settings.Default.Bg4Color7R;
            bg[3][6].Green = Settings.Default.Bg4Color7G;
            bg[3][6].Blue = Settings.Default.Bg4Color7B;
            bg[4][0].Red = Settings.Default.Bg5Color1R;
            bg[4][0].Green = Settings.Default.Bg5Color1G;
            bg[4][0].Blue = Settings.Default.Bg5Color1B;
            bg[4][1].Red = Settings.Default.Bg5Color2R;
            bg[4][1].Green = Settings.Default.Bg5Color2G;
            bg[4][1].Blue = Settings.Default.Bg5Color2B;
            bg[4][2].Red = Settings.Default.Bg5Color3R;
            bg[4][2].Green = Settings.Default.Bg5Color3G;
            bg[4][2].Blue = Settings.Default.Bg5Color3B;
            bg[4][3].Red = Settings.Default.Bg5Color4R;
            bg[4][3].Green = Settings.Default.Bg5Color4G;
            bg[4][3].Blue = Settings.Default.Bg5Color4B;
            bg[4][4].Red = Settings.Default.Bg5Color5R;
            bg[4][4].Green = Settings.Default.Bg5Color5G;
            bg[4][4].Blue = Settings.Default.Bg5Color5B;
            bg[4][5].Red = Settings.Default.Bg5Color6R;
            bg[4][5].Green = Settings.Default.Bg5Color6G;
            bg[4][5].Blue = Settings.Default.Bg5Color6B;
            bg[4][6].Red = Settings.Default.Bg5Color7R;
            bg[4][6].Green = Settings.Default.Bg5Color7G;
            bg[4][6].Blue = Settings.Default.Bg5Color7B;
            bg[5][0].Red = Settings.Default.Bg6Color1R;
            bg[5][0].Green = Settings.Default.Bg6Color1G;
            bg[5][0].Blue = Settings.Default.Bg6Color1B;
            bg[5][1].Red = Settings.Default.Bg6Color2R;
            bg[5][1].Green = Settings.Default.Bg6Color2G;
            bg[5][1].Blue = Settings.Default.Bg6Color2B;
            bg[5][2].Red = Settings.Default.Bg6Color3R;
            bg[5][2].Green = Settings.Default.Bg6Color3G;
            bg[5][2].Blue = Settings.Default.Bg6Color3B;
            bg[5][3].Red = Settings.Default.Bg6Color4R;
            bg[5][3].Green = Settings.Default.Bg6Color4G;
            bg[5][3].Blue = Settings.Default.Bg6Color4B;
            bg[5][4].Red = Settings.Default.Bg6Color5R;
            bg[5][4].Green = Settings.Default.Bg6Color5G;
            bg[5][4].Blue = Settings.Default.Bg6Color5B;
            bg[5][5].Red = Settings.Default.Bg6Color6R;
            bg[5][5].Green = Settings.Default.Bg6Color6G;
            bg[5][5].Blue = Settings.Default.Bg6Color6B;
            bg[5][6].Red = Settings.Default.Bg6Color7R;
            bg[5][6].Green = Settings.Default.Bg6Color7G;
            bg[5][6].Blue = Settings.Default.Bg6Color7B;
            bg[6][0].Red = Settings.Default.Bg7Color1R;
            bg[6][0].Green = Settings.Default.Bg7Color1G;
            bg[6][0].Blue = Settings.Default.Bg7Color1B;
            bg[6][1].Red = Settings.Default.Bg7Color2R;
            bg[6][1].Green = Settings.Default.Bg7Color2G;
            bg[6][1].Blue = Settings.Default.Bg7Color2B;
            bg[6][2].Red = Settings.Default.Bg7Color3R;
            bg[6][2].Green = Settings.Default.Bg7Color3G;
            bg[6][2].Blue = Settings.Default.Bg7Color3B;
            bg[6][3].Red = Settings.Default.Bg7Color4R;
            bg[6][3].Green = Settings.Default.Bg7Color4G;
            bg[6][3].Blue = Settings.Default.Bg7Color4B;
            bg[6][4].Red = Settings.Default.Bg7Color5R;
            bg[6][4].Green = Settings.Default.Bg7Color5G;
            bg[6][4].Blue = Settings.Default.Bg7Color5B;
            bg[6][5].Red = Settings.Default.Bg7Color6R;
            bg[6][5].Green = Settings.Default.Bg7Color6G;
            bg[6][5].Blue = Settings.Default.Bg7Color6B;
            bg[6][6].Red = Settings.Default.Bg7Color7R;
            bg[6][6].Green = Settings.Default.Bg7Color7G;
            bg[6][6].Blue = Settings.Default.Bg7Color7B;
            bg[7][0].Red = Settings.Default.Bg8Color1R;
            bg[7][0].Green = Settings.Default.Bg8Color1G;
            bg[7][0].Blue = Settings.Default.Bg8Color1B;
            bg[7][1].Red = Settings.Default.Bg8Color2R;
            bg[7][1].Green = Settings.Default.Bg8Color2G;
            bg[7][1].Blue = Settings.Default.Bg8Color2B;
            bg[7][2].Red = Settings.Default.Bg8Color3R;
            bg[7][2].Green = Settings.Default.Bg8Color3G;
            bg[7][2].Blue = Settings.Default.Bg8Color3B;
            bg[7][3].Red = Settings.Default.Bg8Color4R;
            bg[7][3].Green = Settings.Default.Bg8Color4G;
            bg[7][3].Blue = Settings.Default.Bg8Color4B;
            bg[7][4].Red = Settings.Default.Bg8Color5R;
            bg[7][4].Green = Settings.Default.Bg8Color5G;
            bg[7][4].Blue = Settings.Default.Bg8Color5B;
            bg[7][5].Red = Settings.Default.Bg8Color6R;
            bg[7][5].Green = Settings.Default.Bg8Color6G;
            bg[7][5].Blue = Settings.Default.Bg8Color6B;
            bg[7][6].Red = Settings.Default.Bg8Color7R;
            bg[7][6].Green = Settings.Default.Bg8Color7G;
            bg[7][6].Blue = Settings.Default.Bg8Color7B;

            rom.Seek(0x2d1c02, SeekOrigin.Begin);
            rom.Write(bg[0][0].ToRomBytes(), 0, 2);
            rom.Seek(0x2d1c04, SeekOrigin.Begin);
            rom.Write(bg[0][1].ToRomBytes(), 0, 2);
            rom.Seek(0x2d1c06, SeekOrigin.Begin);
            rom.Write(bg[0][2].ToRomBytes(), 0, 2);
            rom.Seek(0x2d1c08, SeekOrigin.Begin);
            rom.Write(bg[0][3].ToRomBytes(), 0, 2);
            rom.Seek(0x2d1c0a, SeekOrigin.Begin);
            rom.Write(bg[0][4].ToRomBytes(), 0, 2);
            rom.Seek(0x2d1c0c, SeekOrigin.Begin);
            rom.Write(bg[0][5].ToRomBytes(), 0, 2);
            rom.Seek(0x2d1c0e, SeekOrigin.Begin);
            rom.Write(bg[0][6].ToRomBytes(), 0, 2);

            rom.Seek(0x2d1c22, SeekOrigin.Begin);
            rom.Write(bg[1][0].ToRomBytes(), 0, 2);
            rom.Seek(0x2d1c24, SeekOrigin.Begin);
            rom.Write(bg[1][1].ToRomBytes(), 0, 2);
            rom.Seek(0x2d1c26, SeekOrigin.Begin);
            rom.Write(bg[1][2].ToRomBytes(), 0, 2);
            rom.Seek(0x2d1c28, SeekOrigin.Begin);
            rom.Write(bg[1][3].ToRomBytes(), 0, 2);
            rom.Seek(0x2d1c2a, SeekOrigin.Begin);
            rom.Write(bg[1][4].ToRomBytes(), 0, 2);
            rom.Seek(0x2d1c2c, SeekOrigin.Begin);
            rom.Write(bg[1][5].ToRomBytes(), 0, 2);
            rom.Seek(0x2d1c2e, SeekOrigin.Begin);
            rom.Write(bg[1][6].ToRomBytes(), 0, 2);

            rom.Seek(0x2d1c42, SeekOrigin.Begin);
            rom.Write(bg[2][0].ToRomBytes(), 0, 2);
            rom.Seek(0x2d1c44, SeekOrigin.Begin);
            rom.Write(bg[2][1].ToRomBytes(), 0, 2);
            rom.Seek(0x2d1c46, SeekOrigin.Begin);
            rom.Write(bg[2][2].ToRomBytes(), 0, 2);
            rom.Seek(0x2d1c48, SeekOrigin.Begin);
            rom.Write(bg[2][3].ToRomBytes(), 0, 2);
            rom.Seek(0x2d1c4a, SeekOrigin.Begin);
            rom.Write(bg[2][4].ToRomBytes(), 0, 2);
            rom.Seek(0x2d1c4c, SeekOrigin.Begin);
            rom.Write(bg[2][5].ToRomBytes(), 0, 2);
            rom.Seek(0x2d1c4e, SeekOrigin.Begin);
            rom.Write(bg[2][6].ToRomBytes(), 0, 2);

            rom.Seek(0x2d1c62, SeekOrigin.Begin);
            rom.Write(bg[3][0].ToRomBytes(), 0, 2);
            rom.Seek(0x2d1c64, SeekOrigin.Begin);
            rom.Write(bg[3][1].ToRomBytes(), 0, 2);
            rom.Seek(0x2d1c66, SeekOrigin.Begin);
            rom.Write(bg[3][2].ToRomBytes(), 0, 2);
            rom.Seek(0x2d1c68, SeekOrigin.Begin);
            rom.Write(bg[3][3].ToRomBytes(), 0, 2);
            rom.Seek(0x2d1c6a, SeekOrigin.Begin);
            rom.Write(bg[3][4].ToRomBytes(), 0, 2);
            rom.Seek(0x2d1c6c, SeekOrigin.Begin);
            rom.Write(bg[3][5].ToRomBytes(), 0, 2);
            rom.Seek(0x2d1c6e, SeekOrigin.Begin);
            rom.Write(bg[3][6].ToRomBytes(), 0, 2);

            rom.Seek(0x2d1c82, SeekOrigin.Begin);
            rom.Write(bg[4][0].ToRomBytes(), 0, 2);
            rom.Seek(0x2d1c84, SeekOrigin.Begin);
            rom.Write(bg[4][1].ToRomBytes(), 0, 2);
            rom.Seek(0x2d1c86, SeekOrigin.Begin);
            rom.Write(bg[4][2].ToRomBytes(), 0, 2);
            rom.Seek(0x2d1c88, SeekOrigin.Begin);
            rom.Write(bg[4][3].ToRomBytes(), 0, 2);
            rom.Seek(0x2d1c8a, SeekOrigin.Begin);
            rom.Write(bg[4][4].ToRomBytes(), 0, 2);
            rom.Seek(0x2d1c8c, SeekOrigin.Begin);
            rom.Write(bg[4][5].ToRomBytes(), 0, 2);
            rom.Seek(0x2d1c8e, SeekOrigin.Begin);
            rom.Write(bg[4][6].ToRomBytes(), 0, 2);

            rom.Seek(0x2d1ca2, SeekOrigin.Begin);
            rom.Write(bg[5][0].ToRomBytes(), 0, 2);
            rom.Seek(0x2d1ca4, SeekOrigin.Begin);
            rom.Write(bg[5][1].ToRomBytes(), 0, 2);
            rom.Seek(0x2d1ca6, SeekOrigin.Begin);
            rom.Write(bg[5][2].ToRomBytes(), 0, 2);
            rom.Seek(0x2d1ca8, SeekOrigin.Begin);
            rom.Write(bg[5][3].ToRomBytes(), 0, 2);
            rom.Seek(0x2d1caa, SeekOrigin.Begin);
            rom.Write(bg[5][4].ToRomBytes(), 0, 2);
            rom.Seek(0x2d1cac, SeekOrigin.Begin);
            rom.Write(bg[5][5].ToRomBytes(), 0, 2);
            rom.Seek(0x2d1cae, SeekOrigin.Begin);
            rom.Write(bg[5][6].ToRomBytes(), 0, 2);

            rom.Seek(0x2d1cc2, SeekOrigin.Begin);
            rom.Write(bg[6][0].ToRomBytes(), 0, 2);
            rom.Seek(0x2d1cc4, SeekOrigin.Begin);
            rom.Write(bg[6][1].ToRomBytes(), 0, 2);
            rom.Seek(0x2d1cc6, SeekOrigin.Begin);
            rom.Write(bg[6][2].ToRomBytes(), 0, 2);
            rom.Seek(0x2d1cc8, SeekOrigin.Begin);
            rom.Write(bg[6][3].ToRomBytes(), 0, 2);
            rom.Seek(0x2d1cca, SeekOrigin.Begin);
            rom.Write(bg[6][4].ToRomBytes(), 0, 2);
            rom.Seek(0x2d1ccc, SeekOrigin.Begin);
            rom.Write(bg[6][5].ToRomBytes(), 0, 2);
            rom.Seek(0x2d1cce, SeekOrigin.Begin);
            rom.Write(bg[6][6].ToRomBytes(), 0, 2);

            rom.Seek(0x2d1ce2, SeekOrigin.Begin);
            rom.Write(bg[7][0].ToRomBytes(), 0, 2);
            rom.Seek(0x2d1ce4, SeekOrigin.Begin);
            rom.Write(bg[7][1].ToRomBytes(), 0, 2);
            rom.Seek(0x2d1ce6, SeekOrigin.Begin);
            rom.Write(bg[7][2].ToRomBytes(), 0, 2);
            rom.Seek(0x2d1ce8, SeekOrigin.Begin);
            rom.Write(bg[7][3].ToRomBytes(), 0, 2);
            rom.Seek(0x2d1cea, SeekOrigin.Begin);
            rom.Write(bg[7][4].ToRomBytes(), 0, 2);
            rom.Seek(0x2d1cec, SeekOrigin.Begin);
            rom.Write(bg[7][5].ToRomBytes(), 0, 2);
            rom.Seek(0x2d1cee, SeekOrigin.Begin);
            rom.Write(bg[7][6].ToRomBytes(), 0, 2);
        }

        private void WriteFontColor(FileStream rom)
        {
            var fontColor = new Ff6Color
                                {
                                    Red = Settings.Default.FontColorR,
                                    Green = Settings.Default.FontColorG,
                                    Blue = Settings.Default.FontColorB
                                };

            rom.Seek(0x3709f, SeekOrigin.Begin);
            rom.Write(fontColor.ToRomBytes(), 0, 2);
        }

        private void WriteColosseum(FileStream rom)
        {
            for (int i = 0x1fb602; i <= 0x1fb9fe; i += 0x04)
            {
                rom.Seek(i, SeekOrigin.Begin);

                var item1 = rom.ReadByte();
                var itemList = new List<Item>();

                RandomizeItem(itemList, item1);

                rom.Seek(i, SeekOrigin.Begin);
                rom.Write(new[] { (byte)itemList[0] }, 0, 1);
            }
        }

        private void WriteSteals(FileStream rom)
        {
            for (int i = 0xf3000; i <= 0xf35ff; i += 0x02)
            {
                rom.Seek(i, SeekOrigin.Begin);

                var item1 = rom.ReadByte();
                var item2 = rom.ReadByte();
                var itemList = new List<Item>();

                RandomizeItem(itemList, item1);
                RandomizeItem(itemList, item2);

                rom.Seek(i, SeekOrigin.Begin);
                rom.Write(new[] { (byte)itemList[0], (byte)itemList[1] }, 0, 2);
            }
        }

        private void WriteRngTable(FileStream rom)
        {
            var numList = new List<byte>();
            var rngTable = new List<byte>();

            for (int i = 0x00; i < 0x100; i++)
            {
                numList.Add((byte)i);
            }

            while (numList.Count > 0)
            {
                var i = random.Next(numList.Count);
                rngTable.Add(numList[i]);
                numList.RemoveAt(i);
            }

            rom.Seek(0xfd00, SeekOrigin.Begin);
            rom.Write(rngTable.ToArray(), 0, rngTable.Count);
        }

        private void WriteStartingMagic(FileStream rom)
        {
            WriteStartingLores(rom);
            WriteStartingRages(rom);
            WriteNaturalMagic(rom);
        }

        private static void WriteNaturalMagic(FileStream rom)
        {
            var morphMagicList = new List<Spell>();

            AddSpell(morphMagicList, MagicTiers.GetMagicTiers().Tiers[0]);
            AddSpell(morphMagicList, MagicTiers.GetMagicTiers().Tiers[0]);
            AddSpell(morphMagicList, MagicTiers.GetMagicTiers().Tiers[0]);
            AddSpell(morphMagicList, MagicTiers.GetMagicTiers().Tiers[random.Next(2)]);
            AddSpell(morphMagicList, MagicTiers.GetMagicTiers().Tiers[1]);
            AddSpell(morphMagicList, MagicTiers.GetMagicTiers().Tiers[1]);
            AddSpell(morphMagicList, MagicTiers.GetMagicTiers().Tiers[1]);
            AddSpell(morphMagicList, MagicTiers.GetMagicTiers().Tiers[1]);
            AddSpell(morphMagicList, MagicTiers.GetMagicTiers().Tiers[random.Next(1, 3)]);
            AddSpell(morphMagicList, MagicTiers.GetMagicTiers().Tiers[random.Next(1, 3)]);
            AddSpell(morphMagicList, MagicTiers.GetMagicTiers().Tiers[2]);
            AddSpell(morphMagicList, MagicTiers.GetMagicTiers().Tiers[2]);
            AddSpell(morphMagicList, MagicTiers.GetMagicTiers().Tiers[2]);
            AddSpell(morphMagicList, MagicTiers.GetMagicTiers().Tiers[random.Next(2, 4)]);
            AddSpell(morphMagicList, MagicTiers.GetMagicTiers().Tiers[3]);
            AddSpell(morphMagicList, MagicTiers.GetMagicTiers().Tiers[3]);

            for (int i = 0; i < morphMagicList.Count; i++)
            {
                rom.Seek(0x2ce3c0 + (2 * i), SeekOrigin.Begin);
                rom.Write(new[] { (byte)morphMagicList[i] }, 0, 1);
            }

            var runicMagicList = new List<Spell>();

            AddSpell(runicMagicList, MagicTiers.GetMagicTiers().Tiers[0]);
            AddSpell(runicMagicList, MagicTiers.GetMagicTiers().Tiers[0]);
            AddSpell(runicMagicList, MagicTiers.GetMagicTiers().Tiers[0]);
            AddSpell(runicMagicList, MagicTiers.GetMagicTiers().Tiers[random.Next(2)]);
            AddSpell(runicMagicList, MagicTiers.GetMagicTiers().Tiers[1]);
            AddSpell(runicMagicList, MagicTiers.GetMagicTiers().Tiers[1]);
            AddSpell(runicMagicList, MagicTiers.GetMagicTiers().Tiers[1]);
            AddSpell(runicMagicList, MagicTiers.GetMagicTiers().Tiers[1]);
            AddSpell(runicMagicList, MagicTiers.GetMagicTiers().Tiers[random.Next(1, 3)]);
            AddSpell(runicMagicList, MagicTiers.GetMagicTiers().Tiers[random.Next(1, 3)]);
            AddSpell(runicMagicList, MagicTiers.GetMagicTiers().Tiers[2]);
            AddSpell(runicMagicList, MagicTiers.GetMagicTiers().Tiers[2]);
            AddSpell(runicMagicList, MagicTiers.GetMagicTiers().Tiers[2]);
            AddSpell(runicMagicList, MagicTiers.GetMagicTiers().Tiers[random.Next(2, 4)]);
            AddSpell(runicMagicList, MagicTiers.GetMagicTiers().Tiers[3]);
            AddSpell(runicMagicList, MagicTiers.GetMagicTiers().Tiers[3]);

            for (int i = 0; i < runicMagicList.Count; i++)
            {
                rom.Seek(0x2ce3e0 + (2 * i), SeekOrigin.Begin);
                rom.Write(new[] { (byte)runicMagicList[i] }, 0, 1);
            }
        }

        private static void WriteStartingRages(FileStream rom)
        {
            // Get 9 Rages at random
            var startingRages = new List<Rage>();
            var rageList = StartingRages.GetStartingRages().Rages;

            var nextRage = random.Next(rageList.Count);
            startingRages.Add(rageList[nextRage]);
            rageList.RemoveAt(nextRage);

            nextRage = random.Next(rageList.Count);
            startingRages.Add(rageList[nextRage]);
            rageList.RemoveAt(nextRage);

            nextRage = random.Next(rageList.Count);
            startingRages.Add(rageList[nextRage]);
            rageList.RemoveAt(nextRage);

            nextRage = random.Next(rageList.Count);
            startingRages.Add(rageList[nextRage]);
            rageList.RemoveAt(nextRage);

            nextRage = random.Next(rageList.Count);
            startingRages.Add(rageList[nextRage]);
            rageList.RemoveAt(nextRage);

            nextRage = random.Next(rageList.Count);
            startingRages.Add(rageList[nextRage]);
            rageList.RemoveAt(nextRage);

            nextRage = random.Next(rageList.Count);
            startingRages.Add(rageList[nextRage]);
            rageList.RemoveAt(nextRage);

            nextRage = random.Next(rageList.Count);
            startingRages.Add(rageList[nextRage]);
            rageList.RemoveAt(nextRage);

            nextRage = random.Next(rageList.Count);
            startingRages.Add(rageList[nextRage]);
            rageList.RemoveAt(nextRage);

            for (int i = 0x47aa0; i < 0x47ac0; i++)
            {
                var valueToWrite = startingRages.Where(rage => rage.Address == i).Aggregate(0, (current, rage) => current | rage.Value);

                rom.Seek(i, SeekOrigin.Begin);
                rom.Write(new[] { (byte)valueToWrite }, 0, 1);
            }
        }

        private static void WriteStartingLores(FileStream rom)
        {
            var loreList = new List<int>
                               {
                                   0x010000, // Condemned
                                   0x020000, // Roulette
                                   0x040000, // CleanSweep
                                   0x080000, // Aqua Rake
                                   0x800000, // Revenge
                                   0x000200, // L.5 Doom
                                   0x000400, // L.3 Muddle
                                   0x000800, // Reflect???
                                   0x004000, // Step Mine
                                   0x008000, // ForceField
                                   0x000002, // Sour Mouth
                                   0x000004, // Pep Up
                                   0x000010, // Stone
                                   0x000080, // Exploder
                               };

            var loreBytes = 0;

            // Get 3 lores at random
            var nextLore = random.Next(loreList.Count);
            loreBytes |= loreList[nextLore];
            loreList.RemoveAt(nextLore);

            nextLore = random.Next(loreList.Count);
            loreBytes |= loreList[nextLore];
            loreList.RemoveAt(nextLore);

            nextLore = random.Next(loreList.Count);
            loreBytes |= loreList[nextLore];
            loreList.RemoveAt(nextLore);

            var byte1 = (byte)(loreBytes >> 16);
            var byte2 = (byte)(loreBytes >> 8);
            var byte3 = (byte)loreBytes;

            rom.Seek(0x26f564, SeekOrigin.Begin);
            rom.Write(new[] { byte1, byte2, byte3 }, 0, 3);
        }

        private static void AddSpell(List<Spell> charMagicList, List<Spell> sourceList)
        {
            var nextSpell = random.Next(sourceList.Count);
            while (charMagicList.Contains(sourceList[nextSpell]))
            {
                nextSpell = random.Next(sourceList.Count);
            }
            charMagicList.Add(sourceList[nextSpell]);
        }

        private void WriteEquippableItems(FileStream rom)
        {
            foreach (var item in EquippableItems.GetEquippableItems().Items)
            {
                WriteItem(rom, item);
            }
        }

        private void WriteItem(FileStream rom, InventoryItem item)
        {
            var itemAddr = 0x185001 + 0x1E * (int)item.Type;
            rom.Seek(itemAddr, SeekOrigin.Begin);

            var byte1 = rom.ReadByte();
            var byte2 = rom.ReadByte();
            WriteCharactersToBytes(ref byte1, ref byte2, item);

            rom.Seek(itemAddr, SeekOrigin.Begin);
            rom.Write(new[] { (byte)byte1, (byte)byte2 }, 0, 2);
        }

        private void WriteCharactersToBytes(ref int byte1, ref int byte2, InventoryItem item)
        {
            byte1 = 0x00;
            byte2 &= 0xC0;

            foreach (var characterId in item.CharacterIds)
            {
                var characterBit = (1 << characterId);
                if (characterBit > 255)
                {
                    characterBit >>= 8;
                }
                if (characterId < 8)
                {
                    byte1 |= characterBit;
                }
                else
                {
                    byte2 |= characterBit;
                }
            }
        }

        private byte[] GetSpellAddress(int input)
        {
            var address = 0x1a6e + (input * 0x36);
            var retVal = new[]
                             {
                                 (byte) address,
                                 (byte)(((uint)address >> 8) & 0xFF),
                             };

            return retVal;
        }

        private void WriteMorphBytes(FileStream rom)
        {
            rom.Seek(0xa183, SeekOrigin.Begin);
            rom.Write(new[] { (byte)morphCharacterId }, 0, 1);
            rom.Seek(0x25e33, SeekOrigin.Begin);
            rom.Write(new[] { (byte)morphCharacterId }, 0, 1);
            rom.Seek(0x261ba, SeekOrigin.Begin);
            rom.Write(new[] { (byte)morphCharacterId }, 0, 1);

            var spellAddr = GetSpellAddress(morphCharacterId);
            rom.Seek(0xa1ac, SeekOrigin.Begin);
            rom.Write(spellAddr, 0, 2);
        }

        private void WriteRunicBytes(FileStream rom)
        {
            rom.Seek(0xa187, SeekOrigin.Begin);
            rom.Write(new[] { (byte)runicCharacterId }, 0, 1);
            rom.Seek(0x261c1, SeekOrigin.Begin);
            rom.Write(new[] { (byte)runicCharacterId }, 0, 1);

            var spellAddr = GetSpellAddress(runicCharacterId);
            rom.Seek(0xa1ce, SeekOrigin.Begin);
            rom.Write(spellAddr, 0, 2);
        }

        private void WriteSwdTechBytes(FileStream rom)
        {
            rom.Seek(0xa18b, SeekOrigin.Begin);
            rom.Write(new[] { (byte)swdTechCharacterId }, 0, 1);
            rom.Seek(0x261c8, SeekOrigin.Begin);
            rom.Write(new[] { (byte)swdTechCharacterId }, 0, 1);
        }

        private void WriteBlitzBytes(FileStream rom)
        {
            rom.Seek(0xa18f, SeekOrigin.Begin);
            rom.Write(new[] { (byte)blitzCharacterId }, 0, 1);
            rom.Seek(0x261e4, SeekOrigin.Begin);
            rom.Write(new[] { (byte)blitzCharacterId }, 0, 1);
        }

        private void WriteDanceBytes(FileStream rom)
        {
            rom.Seek(0x25ee6, SeekOrigin.Begin);
            rom.Write(new[] { (byte)danceCharacterId }, 0, 1);
        }

        private void WriteLoreBytes(FileStream rom)
        {
            rom.Seek(0x236e2, SeekOrigin.Begin);
            rom.Write(new[] { (byte)loreCharacterId }, 0, 1);
        }

        private void WriteGogoBytes(FileStream rom)
        {
            rom.Seek(0x31c68, SeekOrigin.Begin);
            rom.Write(new[] { (byte)gogoCharacterId }, 0, 1);
            rom.Seek(0x3225f, SeekOrigin.Begin);
            rom.Write(new[] { (byte)gogoCharacterId }, 0, 1);
        }

        private void WriteUmaroBytes(FileStream rom)
        {
            rom.Seek(0x20927, SeekOrigin.Begin);
            rom.Write(new[] { (byte)umaroCharacterId }, 0, 1);
            rom.Seek(0x21618, SeekOrigin.Begin);
            rom.Write(new[] { (byte)umaroCharacterId }, 0, 1);
        }

        private void WriteRageBytes(FileStream rom)
        {
            rom.Seek(0x25366, SeekOrigin.Begin);
            rom.Write(new[] { (byte)rageCharacterId }, 0, 1);
        }

        private void WriteCharacter(FileStream rom, Character character)
        {
            WriteString(rom, character.Name);

            foreach (var palette in character.PaletteOverworld)
            {
                WriteByte(rom, palette);
            }

            foreach (var palette in character.PaletteNpcs)
            {
                WriteNpcByte(rom, palette);
            }

            if (character.PaletteAltNpcs != null)
            {
                foreach (var palette in character.PaletteAltNpcs)
                {
                    WriteNpcByte(rom, palette);
                }
            }

            foreach (var palette in character.PaletteShops)
            {
                WriteByte(rom, palette);
            }

            WriteByte(rom, character.HP);
            WriteByte(rom, character.MP);
            WriteByte(rom, character.Menu1);
            WriteByte(rom, character.Menu2);
            WriteByte(rom, character.Menu3);
            WriteByte(rom, character.Menu4);
            WriteByte(rom, character.Vigor);
            WriteByte(rom, character.Speed);
            WriteByte(rom, character.Stamina);
            WriteByte(rom, character.MagicPower);
            WriteByte(rom, character.BattlePower);
            WriteByte(rom, character.Defense);
            WriteByte(rom, character.MagicDefense);
            WriteByte(rom, character.Evade);
            WriteByte(rom, character.MagicBlock);
            WriteByte(rom, character.Hand1);
            WriteByte(rom, character.Hand2);
            WriteByte(rom, character.Head);
            WriteByte(rom, character.Body);
            WriteByte(rom, character.Relic1);
            WriteByte(rom, character.Relic2);
            WriteByte(rom, character.RunSuccess);
        }

        private void WriteNpcByte(FileStream rom, RomAddress<int> input)
        {
            rom.Seek(input.Address, SeekOrigin.Begin);

            var currentByte = rom.ReadByte();
            currentByte = (currentByte & 0xE3) | (input.Value * 4);

            rom.Seek(-1, SeekOrigin.Current);
            rom.Write(new[] { (byte)currentByte }, 0, 1);
        }

        private void WriteByte(FileStream rom, RomAddress<Item> input)
        {
            rom.Seek(input.Address, SeekOrigin.Begin);
            rom.Write(new[] { (byte)input.Value }, 0, 1);
        }

        private void WriteByte(FileStream rom, RomAddress<MenuItem> input)
        {
            rom.Seek(input.Address, SeekOrigin.Begin);
            rom.Write(new[] { (byte)input.Value }, 0, 1);
        }

        private void WriteByte(FileStream rom, RomAddress<int> input)
        {
            rom.Seek(input.Address, SeekOrigin.Begin);
            rom.Write(new[] { (byte)input.Value }, 0, 1);
        }

        private void WriteString(FileStream rom, RomAddress<string> input)
        {
            rom.Seek(input.Address, SeekOrigin.Begin);
            var outString = StringToByteArray(ConvertStringToFF6(input.Value));
            rom.Write(outString, 0, 6);
        }

        private string ConvertStringToFF6(string input)
        {
            var retVal = new StringBuilder();

            for (int i = 0; i < 6; i++)
            {
                retVal.Append(i >= input.Length ? '\xFF' : ConvertCharacter(input[i]));
            }

            return retVal.ToString();
        }

        private char ConvertCharacter(char input)
        {
            if (input >= 'A' && input <= 'Z')
                return (char)(input + 0x3f);
            if (input >= 'a' && input <= 'z')
                return (char)(input + 0x39);
            if (input >= '0' && input <= '9')
                return (char)(input + 0x84);
            if (input == '.')
                return (char)(input + 0x97);
            return input;
        }

        private void InitializeCommands()
        {
            commands = new List<MenuItem>
                           {
                               MenuItem.Morph,
                               MenuItem.Steal,
                               MenuItem.SwdTech,
                               MenuItem.Throw,
                               MenuItem.Tools,
                               MenuItem.Blitz,
                               MenuItem.Runic,
                               MenuItem.Lore,
                               MenuItem.Sketch,
                               MenuItem.Slot,
                               MenuItem.Dance,
                               MenuItem.Rage,
                               MenuItem.Mimic,
                               MenuItem.Empty
                           };
        }

        private void RandomizeCharacters()
        {
            var characters = AllCharacters.GetAllCharacters().Characters;

            RandomizeCharacter(characters[0], false, 0);
            RandomizeCharacter(characters[1], true, 1);
            RandomizeCharacter(characters[2], true, 2);
            RandomizeCharacter(characters[3], true, 3);
            RandomizeCharacter(characters[4], true, 4);
            RandomizeCharacter(characters[5], true, 5);
            RandomizeCharacter(characters[6], false, 6);
            RandomizeCharacter(characters[7], true, 7);
            RandomizeCharacter(characters[8], false, 8);
            RandomizeCharacter(characters[9], true, 9);
            RandomizeCharacter(characters[10], true, 10);
            RandomizeCharacter(characters[11], true, 11);
            RandomizeCharacter(characters[12], true, 12);
            RandomizeCharacter(characters[13], true, 13);
        }

        private void RandomizeCharacter(Character character, bool isMale, int characterId)
        {
            if (characterId == 12)
            {
                if (random.Next(2) == 1)
                {
                    isMale = !isMale;
                }
            }
            
            SetCharacterName(character.Name, isMale);

            var newPalette = random.Next(6);


            foreach (var palette in character.PaletteOverworld)
            {
                palette.Value = newPalette;
            }

            foreach (var palette in character.PaletteNpcs)
            {
                palette.Value = newPalette;
            }

            if (character.PaletteAltNpcs != null)
            {
                foreach (var palette in character.PaletteAltNpcs)
                {
                    palette.Value = (newPalette + 1) % 6;
                }
            }

            foreach (var palette in character.PaletteShops)
            {
                palette.Value = (newPalette * 2) + 0x35;
            }

            character.HP.Value = random.Next(35, 59);
            character.MP.Value = random.Next(3, 19);

            SetCommands(character, characterId);

            character.Vigor.Value = random.Next(25, 48);
            character.Speed.Value = random.Next(25, 41);
            character.Stamina.Value = random.Next(19, 40);
            character.MagicPower.Value = random.Next(25, 45);
            character.BattlePower.Value = random.Next(10, 27);
            character.Defense.Value = random.Next(33, 54);
            character.MagicDefense.Value = random.Next(20, 37);
            character.Evade.Value = random.Next(4, 29);
            character.MagicBlock.Value = random.Next(1, 13);

            character.RunSuccess.Value = (character.RunSuccess.Value & 0xFC) | random.Next(0, 3);
        }

        private void RandomizeStartingEquipment(Character character, int characterId)
        {
            character.Hand1.Value = GetWorstItem(characterId, ItemOrder.GetItemOrder().Weapons);
            character.Hand2.Value = GetWorstItem(characterId, ItemOrder.GetItemOrder().Shields);
            character.Head.Value = GetWorstItem(characterId, ItemOrder.GetItemOrder().Hats);
            character.Body.Value = GetWorstItem(characterId, ItemOrder.GetItemOrder().Armors);
            character.Relic1.Value = Item.Empty;
            character.Relic2.Value = Item.Empty;
        }

        private Item GetWorstItem(int characterId, IEnumerable<Item> items)
        {
            foreach (var item in items.Where(item => EquippableItems.GetEquippableItems().Items[(int)item].CharacterIds.Contains(characterId)))
            {
                return item;
            }

            return Item.Empty;
        }

        private void SetCommands(Character character, int characterId)
        {
            var command = commands[random.Next(commands.Count)];
            commands.Remove(command);

            switch (command)
            {
                case MenuItem.Morph:
                    character.Menu1.Value = MenuItem.Fight;
                    character.Menu2.Value = MenuItem.Morph;
                    character.Menu3.Value = MenuItem.Magic;
                    character.Menu4.Value = MenuItem.Item;
                    morphCharacterId = characterId;
                    break;
                case MenuItem.Steal:
                    character.Menu1.Value = MenuItem.Fight;
                    character.Menu2.Value = MenuItem.Steal;
                    character.Menu3.Value = MenuItem.Magic;
                    character.Menu4.Value = MenuItem.Item;
                    stealCharacterId = characterId;
                    break;
                case MenuItem.SwdTech:
                    character.Menu1.Value = MenuItem.Fight;
                    character.Menu2.Value = MenuItem.SwdTech;
                    character.Menu3.Value = MenuItem.Magic;
                    character.Menu4.Value = MenuItem.Item;
                    swdTechCharacterId = characterId;
                    break;
                case MenuItem.Throw:
                    character.Menu1.Value = MenuItem.Fight;
                    character.Menu2.Value = MenuItem.Throw;
                    character.Menu3.Value = MenuItem.Magic;
                    character.Menu4.Value = MenuItem.Item;
                    break;
                case MenuItem.Tools:
                    character.Menu1.Value = MenuItem.Fight;
                    character.Menu2.Value = MenuItem.Tools;
                    character.Menu3.Value = MenuItem.Magic;
                    character.Menu4.Value = MenuItem.Item;
                    break;
                case MenuItem.Blitz:
                    character.Menu1.Value = MenuItem.Fight;
                    character.Menu2.Value = MenuItem.Blitz;
                    character.Menu3.Value = MenuItem.Magic;
                    character.Menu4.Value = MenuItem.Item;
                    blitzCharacterId = characterId;
                    break;
                case MenuItem.Runic:
                    character.Menu1.Value = MenuItem.Fight;
                    character.Menu2.Value = MenuItem.Runic;
                    character.Menu3.Value = MenuItem.Magic;
                    character.Menu4.Value = MenuItem.Item;
                    runicCharacterId = characterId;
                    break;
                case MenuItem.Lore:
                    character.Menu1.Value = MenuItem.Fight;
                    character.Menu2.Value = MenuItem.Lore;
                    character.Menu3.Value = MenuItem.Magic;
                    character.Menu4.Value = MenuItem.Item;
                    loreCharacterId = characterId;
                    break;
                case MenuItem.Sketch:
                    character.Menu1.Value = MenuItem.Fight;
                    character.Menu2.Value = MenuItem.Sketch;
                    character.Menu3.Value = MenuItem.Magic;
                    character.Menu4.Value = MenuItem.Item;
                    sketchCharacterId = characterId;
                    break;
                case MenuItem.Slot:
                    character.Menu1.Value = MenuItem.Fight;
                    character.Menu2.Value = MenuItem.Slot;
                    character.Menu3.Value = MenuItem.Magic;
                    character.Menu4.Value = MenuItem.Item;
                    slotCharacterId = characterId;
                    break;
                case MenuItem.Dance:
                    character.Menu1.Value = MenuItem.Fight;
                    character.Menu2.Value = MenuItem.Dance;
                    character.Menu3.Value = MenuItem.Magic;
                    character.Menu4.Value = MenuItem.Item;
                    danceCharacterId = characterId;
                    break;
                case MenuItem.Rage:
                    character.Menu1.Value = MenuItem.Rage;
                    character.Menu2.Value = MenuItem.Empty;
                    character.Menu3.Value = MenuItem.Magic;
                    character.Menu4.Value = MenuItem.Item;
                    rageCharacterId = characterId;
                    break;
                case MenuItem.Mimic:
                    character.Menu1.Value = MenuItem.Mimic;
                    character.Menu2.Value = MenuItem.Empty;
                    character.Menu3.Value = MenuItem.Empty;
                    character.Menu4.Value = MenuItem.Empty;
                    gogoCharacterId = characterId;
                    break;
                case MenuItem.Empty:
                    character.Menu1.Value = MenuItem.Empty;
                    character.Menu2.Value = MenuItem.Empty;
                    character.Menu3.Value = MenuItem.Empty;
                    character.Menu4.Value = MenuItem.Empty;
                    umaroCharacterId = characterId;
                    break;
            }

            if (characterId == 13 && character.Menu3.Value == MenuItem.Magic)
            {
                var newCommands = new List<MenuItem>
                                      {
                                          MenuItem.Morph,
                                          MenuItem.Steal,
                                          MenuItem.SwdTech,
                                          MenuItem.Throw,
                                          MenuItem.Tools,
                                          MenuItem.Blitz,
                                          MenuItem.Runic,
                                          MenuItem.Lore,
                                          MenuItem.Sketch,
                                          MenuItem.Slot,
                                          MenuItem.Rage,
                                          MenuItem.Mimic,
                                          MenuItem.Dance
                                      };

                do
                {
                    character.Menu3.Value = newCommands[random.Next(newCommands.Count)];
                } while (character.Menu2.Value == character.Menu3.Value);

                umaroThird = character.Menu3.Value;
            }
        }

        private void SetCharacterName(RomAddress<string> name, bool isMale)
        {
            if (isMale)
            {
                name.Value = maleNames[random.Next(maleNames.Count)];
                maleNames.Remove(name.Value);
                if (name.Value == "GOGO" || name.Value == "QUINA")
                {
                    femaleNames.Remove(name.Value);
                }
            }
            else
            {
                name.Value = femaleNames[random.Next(femaleNames.Count)];
                femaleNames.Remove(name.Value);
                if (name.Value == "GOGO" || name.Value == "QUINA")
                {
                    maleNames.Remove(name.Value);
                }
            }
        }

        private void InitializeNames()
        {
            maleNames = new List<string>
                            {
                                "FIRION",
                                "GUY",
                                "LEON",
                                "MINWU",
                                "JOSEF",
                                "GORDON",
                                "RICARD",
                                "SCOTT",
                                "LUNETH",
                                "ARC",
                                "INGUS",
                                "CECIL",
                                "KAIN",
                                "CID",
                                "PALOM",
                                "YANG",
                                "TELLAH",
                                "EDWARD",
                                "FUSOYA",
                                "EDGE",
                                "BUTZ",
                                "GALUF",
                                "LOCKE",
                                "EDGAR",
                                "SABIN",
                                "CYAN",
                                "SHADOW",
                                "STRAGO",
                                "GAU",
                                "SETZER",
                                "MOG",
                                "GOGO", //remove if pulled for female list
                                "UMARO",
                                "CLOUD",
                                "BARRET",
                                "RED.13",
                                "CAIT",
                                "VINCE",
                                "ZELL",
                                "IRVINE",
                                "LAGUNA",
                                "KIROS",
                                "WARD",
                                "SEIFER",
                                "ZIDANE",
                                "VIVI",
                                "STEINR",
                                "QUINA", //remove if pulled for female list
                                "AMARNT",
                                "BLANK",
                                "CINNA",
                                "MARCUS",
                                "TIDUS",
                                "AURON",
                                "KIMARI",
                                "WAKKA",
                                "VAAN",
                                "BALTHR",
                                "BASCH",
                                "SNOW",
                                "SAZH",
                                "HOPE",
                                "GADOT",
                            };
            femaleNames = new List<string>
                              {
                                  "MARIA",
                                  "LEILA",
                                  "REFIA",
                                  "ROSA",
                                  "POROM",
                                  "RYDIA",
                                  "LENNA",
                                  "FARIS",
                                  "KRILE",
                                  "TERRA",
                                  "RELM",
                                  "CELES",
                                  "GOGO", //remove if pulled for male list
                                  "TIFA",
                                  "AERIS",
                                  "YUFFIE",
                                  "SQUALL", //shots fired
                                  "QUISTS",
                                  "SELPHI",
                                  "RINOA",
                                  "EDEA",
                                  "DAGGER",
                                  "FREYA",
                                  "QUINA", //remove if pulled for male list
                                  "EIKO",
                                  "BEATRX",
                                  "YUNA",
                                  "LULU",
                                  "RIKKU",
                                  "FRAN",
                                  "ASHE",
                                  "PENELO",
                                  "CLAIRE",
                                  "VANILE",
                                  "FANG",
                              };

        }
    }
}
